﻿/*
*  	Graffiti 1.1.1
*  	______________________________________________________________________
*  	www.nocircleno.com/graffiti/
*/

/*
* 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* 	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* 	OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* 	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* 	HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* 	WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* 	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* 	OTHER DEALINGS IN THE SOFTWARE.
*/

package com.nocircleno.graffiti.tools {
	
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.display.LineScaleMode;
	import flash.display.CapsStyle;
	import flash.display.JointStyle;
	import flash.geom.Rectangle;
	import flash.geom.Point;
	import com.nocircleno.graffiti.tools.ITool;
	import com.nocircleno.graffiti.tools.ToolRenderType;
	import com.nocircleno.graffiti.tools.BrushType;
	
	/**
	* Brush Class allows the user to paint anti-alias shapes to the Canvas.
	*/
	public class Brush implements ITool {
		
		// store local references for performance reasons
		private const atan2:Function = Math.atan2;
		private const abs:Function = Math.abs;
		
		private const RENDER_TYPE:String = ToolRenderType.CONTINUOUS;
	
		private var _size:Number;
		private var _halfSize:Number;
		private var _fourthSize:Number;
		private var _eighthSize:Number;
		private var _color:uint;
		
		private var _mode:String;
		private var _type:String;
		
		/**
		* The <code>Brush</code> constructor.
		* 
		* @param brushSize Size of the brush.
		* @param brushColor Color of the brush.
		* @param brushType Type of Brush
		* @param toolMode Tool mode the Brush will be drawing with.
		*
		* @example The following code creates a Brush instance.
		* <listing version="3.0" >
		* // create a diamond brush of size 8 and the color of red
		* var diamondBrush:Brush = new Brush(8, 0xFF0000, BrushType.DIAMOND);
		* </listing>
		* 
		*/
		public function Brush(brushSize:Number = 4, brushColor:uint = 0x000000, brushType:String = null, toolMode:String = null) {
			
			// store size
			size = brushSize;
			
			// store color
			color = brushColor;
			
			// store type
			type = brushType;
			
			// store mode
			mode = toolMode;
			
		}
		
		/**
		* Size of the Brush
		*/
		public function set size(brushSize:Number):void {
			
			if(brushSize > 0) {
			
				// set brush size
				_size = brushSize;
				
				// calcuate divisions
				_halfSize = _size/2;
				_fourthSize = _size/4;
				_eighthSize = _size/8;
				
			}
			
		}
		
		public function get size():Number {
			return _size;
		}
		
		/**
		* Color of the Brush
		*/
		public function set color(brushColor:uint):void {
			_color = brushColor;
		}

		public function get color():uint {
			return _color;
		}
		
		/**
		* Type of brush
		*/
		public function set type(brushType:String):void {
			
			// determine type
			if(brushType != null && BrushType.validType(brushType)) {
				_type = brushType;
			} else {
				_type = BrushType.SQUARE;
			}
			
		}
		
		public function get type():String {
			return _type;
		}
		
		/**
		* Brush Render Mode
		*/
		public function get renderType():String {
			return RENDER_TYPE;
		}
		
		/**
		* Brush Drawing Mode
		*/
		public function set mode(toolMode:String):void {
			
			// store mode
			if(toolMode != null && ToolMode.validMode(toolMode)) {
				_mode = toolMode;
			} else {
				_mode = ToolMode.NORMAL;
			}
			
		}
		
		public function get mode():String {
			return _mode;
		}
		
		/**
		* The <code>generateBrush</code> method creates a visual representation of the Brush
		* that could be used for a button or a cursor.
		* 
		* @param brushSize Size of the brush, if you don't want to use the defined size.
		* @param brushColor Color of the brush, if you don't want to use the defined color.
		* 
		* @return A Sprite object containing the brush.
		*
		*/
		public function generateBrush(brushSize:Number = 0, brushColor:int = -1):Sprite {
			
			var brush_sp:Sprite = new Sprite();
			
			var xOffset:Number;
			var yOffset:Number;
			
			var genBrushSize:Number;
			var genBrushHalfSize:Number;
			var genBrushFourthSize:Number;
			var genBrushEighthSize:Number;
			var genColor:uint;
			
			// check brush size
			if(brushSize == 0) {
				genBrushSize = _size;
				genBrushHalfSize = _halfSize;
				genBrushFourthSize = _fourthSize;
				genBrushEighthSize = _eighthSize;
			} else {
				genBrushSize = brushSize;
				genBrushHalfSize = brushSize/2;
				genBrushFourthSize = brushSize/4;
				genBrushEighthSize = brushSize/8;
			}
			
			// check brush color
			if(brushColor < 0) {
				genColor = _color;
			} else {
				genColor = brushColor;
			}
			
			//////////////////////////////////////////////////////////
			// Round Shape Brush
			//////////////////////////////////////////////////////////
			if(_type == BrushType.ROUND) {
				
				brush_sp.graphics.beginFill(genColor, 1);
				brush_sp.graphics.drawCircle(0, 0, genBrushHalfSize);
				brush_sp.graphics.endFill();
				
			//////////////////////////////////////////////////////////
			// Rectangle Shape Brushes
			//////////////////////////////////////////////////////////
			} else if(_type == BrushType.HORIZONTAL_LINE || _type == BrushType.VERTICAL_LINE || _type == BrushType.SQUARE) {
				
				if(_type == BrushType.HORIZONTAL_LINE) {
					
					xOffset = genBrushHalfSize;
					yOffset = genBrushEighthSize;
					
				} else if(_type == BrushType.VERTICAL_LINE) {
					
					xOffset = genBrushEighthSize;
					yOffset = genBrushHalfSize;
					
				} else if(_type == BrushType.SQUARE) {
					
					xOffset = genBrushHalfSize;
					yOffset = genBrushHalfSize;
				
				}
				
				brush_sp.graphics.beginFill(genColor, 1);
				brush_sp.graphics.drawRect(- xOffset, - yOffset, xOffset << 1, yOffset << 1);
				brush_sp.graphics.endFill();
			
			//////////////////////////////////////////////////////////
			// Foward Line Shape Brush
			//////////////////////////////////////////////////////////
			} else if(_type == BrushType.FORWARD_LINE) {
				
				brush_sp.graphics.beginFill(genColor, 1);
				brush_sp.graphics.moveTo(genBrushHalfSize - genBrushEighthSize,  - genBrushHalfSize);
				brush_sp.graphics.lineTo(genBrushHalfSize, - genBrushHalfSize);
				brush_sp.graphics.lineTo(-genBrushHalfSize + genBrushEighthSize, genBrushHalfSize);
				brush_sp.graphics.lineTo(-genBrushHalfSize, genBrushHalfSize);
				brush_sp.graphics.lineTo(genBrushHalfSize - genBrushEighthSize,  - genBrushHalfSize);
				brush_sp.graphics.endFill();
				
			//////////////////////////////////////////////////////////
			// Backward Line Shape Brush
			//////////////////////////////////////////////////////////
			} else if(_type == BrushType.BACKWARD_LINE) {
				
				brush_sp.graphics.beginFill(genColor, 1);
				brush_sp.graphics.moveTo(-genBrushHalfSize + genBrushEighthSize, - genBrushHalfSize);
				brush_sp.graphics.lineTo(-genBrushHalfSize, - genBrushHalfSize);
				brush_sp.graphics.lineTo(genBrushHalfSize - genBrushEighthSize, genBrushHalfSize);
				brush_sp.graphics.lineTo(genBrushHalfSize, genBrushHalfSize);
				brush_sp.graphics.lineTo(-genBrushHalfSize + genBrushEighthSize, -genBrushHalfSize);
				brush_sp.graphics.endFill();
			
			//////////////////////////////////////////////////////////
			// Diamond Shape Brush
			//////////////////////////////////////////////////////////
			} else if(_type == BrushType.DIAMOND) {
				
				brush_sp.graphics.beginFill(genColor, 1);
				brush_sp.graphics.moveTo(0, -genBrushHalfSize);
				brush_sp.graphics.lineTo(genBrushHalfSize, 0);
				brush_sp.graphics.lineTo(0, genBrushHalfSize);
				brush_sp.graphics.lineTo(- genBrushHalfSize, 0);
				brush_sp.graphics.lineTo(0, - genBrushHalfSize);
				brush_sp.graphics.endFill();
				
			}
			
			return brush_sp;
			
		}
		
		/**
		* The <code>apply</code> method applies the brush to the Sprite object passed
		* to the method.  We use two point due to the delay in Mouse Events to create
		* continuous  brush strokes.
		* 
		* @param drawingTarget Sprite that the brush will draw to.
		* @param point1 Starting point to apply brush.
		* @param point2 End point to apply brush.
		*/
		public function apply(drawingTarget:DisplayObject, point1:Point, point2:Point = null):void {
			
			// cast target as a Sprite
			var targetCast:Sprite = Sprite(drawingTarget);
			
			var xOffset:Number;
			var yOffset:Number;
			var vPoint1:Point;
			var vPoint2:Point;
			var angleBetweenPoints:Number;
			
			// if we only have one point then draw a single shape of the 
			if(point2 == null) {
				
				//////////////////////////////////////////////////////////
				// Round Shape Brush
				//////////////////////////////////////////////////////////
				if(_type == BrushType.ROUND) {
					
					targetCast.graphics.beginFill(_color, 1);
					targetCast.graphics.drawCircle(point1.x, point1.y, _halfSize);
					targetCast.graphics.endFill();
					
				//////////////////////////////////////////////////////////
				// Rectangle Shape Brushes
				//////////////////////////////////////////////////////////
				} else if(_type == BrushType.HORIZONTAL_LINE || _type == BrushType.VERTICAL_LINE || _type == BrushType.SQUARE) {
					
					if(_type == BrushType.HORIZONTAL_LINE) {
						
						xOffset = _halfSize;
						yOffset = _eighthSize;
						
					} else if(_type == BrushType.VERTICAL_LINE) {
						
						xOffset = _eighthSize;
						yOffset = _halfSize;
						
					} else if(_type == BrushType.SQUARE) {
						
						xOffset = _halfSize;
						yOffset = _halfSize;
					
					}
					
					targetCast.graphics.beginFill(_color, 1);
					targetCast.graphics.drawRect(point1.x - xOffset, point1.y - yOffset, xOffset << 1, yOffset << 1);
					targetCast.graphics.endFill();
				
				//////////////////////////////////////////////////////////
				// Foward Line Shape Brush
				//////////////////////////////////////////////////////////
				} else if(_type == BrushType.FORWARD_LINE) {
					
					targetCast.graphics.beginFill(_color, 1);
					targetCast.graphics.moveTo(point1.x + _halfSize - _eighthSize, point1.y - _halfSize);
					targetCast.graphics.lineTo(point1.x + _halfSize, point1.y - _halfSize);
					targetCast.graphics.lineTo(point1.x - _halfSize + _eighthSize, point1.y + _halfSize);
					targetCast.graphics.lineTo(point1.x - _halfSize, point1.y + _halfSize);
					targetCast.graphics.lineTo(point1.x + _halfSize - _eighthSize, point1.y - _halfSize);
					targetCast.graphics.endFill();
					
				//////////////////////////////////////////////////////////
				// Backward Line Shape Brush
				//////////////////////////////////////////////////////////
				} else if(_type == BrushType.BACKWARD_LINE) {
					
					targetCast.graphics.beginFill(_color, 1);
					targetCast.graphics.moveTo(point1.x - _halfSize + _eighthSize, point1.y - _halfSize);
					targetCast.graphics.lineTo(point1.x - _halfSize, point1.y - _halfSize);
					targetCast.graphics.lineTo(point1.x + _halfSize - _eighthSize, point1.y + _halfSize);
					targetCast.graphics.lineTo(point1.x + _halfSize, point1.y + _halfSize);
					targetCast.graphics.lineTo(point1.x - _halfSize + _eighthSize, point1.y - _halfSize);
					targetCast.graphics.endFill();
				
				//////////////////////////////////////////////////////////
				// Diamond Shape Brush
				//////////////////////////////////////////////////////////
				} else if(_type == BrushType.DIAMOND) {
					
					targetCast.graphics.beginFill(_color, 1);
					targetCast.graphics.moveTo(point1.x, point1.y - _halfSize);
					targetCast.graphics.lineTo(point1.x + _halfSize, point1.y);
					targetCast.graphics.lineTo(point1.x, point1.y + _halfSize);
					targetCast.graphics.lineTo(point1.x - _halfSize, point1.y);
					targetCast.graphics.lineTo(point1.x, point1.y - _halfSize);
					targetCast.graphics.endFill();
					
				}
				
			} else {
				
				// check for order of points
				if(point1.x <= point2.x) {
					vPoint1 = point1;
					vPoint2 = point2;
				} else {
					vPoint1 = point2;
					vPoint2 = point1;
				}
				
				//////////////////////////////////////////////////////////
				// Round Shape Brushes
				//////////////////////////////////////////////////////////
				if(_type == BrushType.ROUND) {
					
					targetCast.graphics.lineStyle(_size, _color, 1, false, LineScaleMode.NORMAL, CapsStyle.ROUND);
					targetCast.graphics.moveTo(point1.x, point1.y);
					targetCast.graphics.lineTo(point2.x, point2.y);
				
				//////////////////////////////////////////////////////////
				// Rectangle Shape Brushes
				//////////////////////////////////////////////////////////
				} else if(_type == BrushType.HORIZONTAL_LINE || _type == BrushType.VERTICAL_LINE || _type == BrushType.SQUARE) {
					
					if(_type == BrushType.HORIZONTAL_LINE) {
						
						xOffset = _halfSize;
						yOffset = _eighthSize;
						
					} else if(_type == BrushType.VERTICAL_LINE) {
						
						xOffset = _eighthSize;
						yOffset = _halfSize;
						
					} else if(_type == BrushType.SQUARE) {
						
						xOffset = _halfSize;
						yOffset = _halfSize;
					
					}
					
					targetCast.graphics.beginFill(_color, 1);
					
					if(vPoint1.y < vPoint2.y) {
						
						targetCast.graphics.moveTo(vPoint1.x + xOffset, vPoint1.y - yOffset);
						targetCast.graphics.lineTo(vPoint2.x + xOffset, vPoint2.y - yOffset);
						targetCast.graphics.lineTo(vPoint2.x + xOffset, vPoint2.y + yOffset);
						targetCast.graphics.lineTo(vPoint2.x - xOffset, vPoint2.y + yOffset);
						targetCast.graphics.lineTo(vPoint1.x - xOffset, vPoint1.y + yOffset);
						targetCast.graphics.lineTo(vPoint1.x - xOffset, vPoint1.y - yOffset);
						targetCast.graphics.lineTo(vPoint1.x + xOffset, vPoint1.y - yOffset);
						
					} else {
						
						targetCast.graphics.moveTo(vPoint1.x - xOffset, vPoint1.y - yOffset);
						targetCast.graphics.lineTo(vPoint2.x - xOffset, vPoint2.y - yOffset);
						targetCast.graphics.lineTo(vPoint2.x + xOffset, vPoint2.y - yOffset);
						targetCast.graphics.lineTo(vPoint2.x + xOffset, vPoint2.y + yOffset);
						targetCast.graphics.lineTo(vPoint1.x + xOffset, vPoint1.y + yOffset);
						targetCast.graphics.lineTo(vPoint1.x - xOffset, vPoint1.y + yOffset);
						targetCast.graphics.lineTo(vPoint1.x - xOffset, vPoint1.y - yOffset);
						
					}
						
					targetCast.graphics.endFill();
					
				//////////////////////////////////////////////////////////
				// Foward Line Shape Brush
				//////////////////////////////////////////////////////////
				} else if(_type == BrushType.FORWARD_LINE) {
					
					angleBetweenPoints = degrees(atan2(vPoint2.x - vPoint1.x, vPoint2.y - vPoint1.y));
					
					targetCast.graphics.beginFill(_color, 1);
					targetCast.graphics.moveTo(vPoint1.x - _halfSize, vPoint1.y + _halfSize);
					
					if(angleBetweenPoints >= 135) {
						
						targetCast.graphics.lineTo(vPoint2.x - _halfSize, vPoint2.y + _halfSize);
						targetCast.graphics.lineTo(vPoint2.x + _halfSize - _eighthSize, vPoint2.y - _halfSize);
						targetCast.graphics.lineTo(vPoint2.x + _halfSize, vPoint2.y - _halfSize);
						targetCast.graphics.lineTo(vPoint1.x + _halfSize, vPoint1.y - _halfSize);
						targetCast.graphics.lineTo(vPoint1.x - _halfSize + _eighthSize, vPoint1.y + _halfSize);
						
					} else if(angleBetweenPoints >= 90) {
					
						targetCast.graphics.lineTo(vPoint1.x + _halfSize - _eighthSize, vPoint1.y - _halfSize);
						targetCast.graphics.lineTo(vPoint2.x + _halfSize - _eighthSize, vPoint2.y - _halfSize);
						targetCast.graphics.lineTo(vPoint2.x + _halfSize, vPoint2.y - _halfSize);
						targetCast.graphics.lineTo(vPoint2.x - _halfSize + _eighthSize, vPoint2.y + _halfSize);
						targetCast.graphics.lineTo(vPoint1.x - _halfSize + _eighthSize, vPoint1.y + _halfSize);
						
					} else {
						
						targetCast.graphics.lineTo(vPoint1.x + _halfSize - _eighthSize, vPoint1.y - _halfSize);
						targetCast.graphics.lineTo(vPoint1.x + _halfSize, vPoint1.y - _halfSize);
						targetCast.graphics.lineTo(vPoint2.x + _halfSize, vPoint2.y - _halfSize);
						targetCast.graphics.lineTo(vPoint2.x - _halfSize + _eighthSize, vPoint2.y + _halfSize);
						targetCast.graphics.lineTo(vPoint2.x - _halfSize, vPoint2.y + _halfSize);
						targetCast.graphics.lineTo(vPoint1.x - _halfSize + _eighthSize, vPoint1.y + _halfSize);
						
					}
					
					targetCast.graphics.lineTo(vPoint1.x - _halfSize, vPoint1.y + _halfSize);
					targetCast.graphics.endFill();
						
				//////////////////////////////////////////////////////////
				// Backward Line Shape Brush
				//////////////////////////////////////////////////////////
				} else if(_type == BrushType.BACKWARD_LINE) {
					
					angleBetweenPoints = degrees(atan2(vPoint2.x - vPoint1.x, vPoint2.y - vPoint1.y));
					
					targetCast.graphics.beginFill(_color, 1);
					targetCast.graphics.moveTo(vPoint1.x - _halfSize, vPoint1.y - _halfSize);
					
					if(angleBetweenPoints <= 45) {
						
						targetCast.graphics.lineTo(vPoint1.x - _halfSize + _eighthSize, vPoint1.y - _halfSize);
						targetCast.graphics.lineTo(vPoint1.x + _halfSize, vPoint1.y + _halfSize);
						targetCast.graphics.lineTo(vPoint2.x + _halfSize, vPoint2.y + _halfSize);						
						targetCast.graphics.lineTo(vPoint2.x + _halfSize - _eighthSize, vPoint2.y + _halfSize);
						targetCast.graphics.lineTo(vPoint2.x - _halfSize, vPoint2.y - _halfSize);
						
					} else if(angleBetweenPoints <= 90) {
					
						targetCast.graphics.lineTo(vPoint1.x - _halfSize + _eighthSize, vPoint1.y - _halfSize);
						targetCast.graphics.lineTo(vPoint2.x - _halfSize + _eighthSize, vPoint2.y - _halfSize);
						targetCast.graphics.lineTo(vPoint2.x + _halfSize, vPoint2.y + _halfSize);						
						targetCast.graphics.lineTo(vPoint2.x + _halfSize - _eighthSize, vPoint2.y + _halfSize);
						targetCast.graphics.lineTo(vPoint1.x + _halfSize - _eighthSize, vPoint1.y + _halfSize);
						
					} else {
						
						targetCast.graphics.lineTo(vPoint2.x - _halfSize, vPoint2.y - _halfSize);
						targetCast.graphics.lineTo(vPoint2.x - _halfSize + _eighthSize, vPoint2.y - _halfSize);
						targetCast.graphics.lineTo(vPoint2.x + _halfSize, vPoint2.y + _halfSize);						
						targetCast.graphics.lineTo(vPoint1.x + _halfSize, vPoint1.y + _halfSize);
						targetCast.graphics.lineTo(vPoint1.x + _halfSize - _eighthSize, vPoint1.y + _halfSize);
						
					}
					
					targetCast.graphics.lineTo(vPoint1.x - _halfSize, vPoint1.y - _halfSize);
					targetCast.graphics.endFill();
				
				//////////////////////////////////////////////////////////
				// Diamond Shape Brush
				//////////////////////////////////////////////////////////
				} else if(_type == BrushType.DIAMOND) {
					
					targetCast.graphics.beginFill(_color, 1);
					
					if(abs(point2.x - point1.x) > abs(point2.y - point1.y)) {	
						
						// draw brush segment
						targetCast.graphics.moveTo(vPoint1.x, vPoint1.y - _halfSize);
						targetCast.graphics.lineTo(vPoint2.x, vPoint2.y - _halfSize);
						targetCast.graphics.lineTo(vPoint2.x + _halfSize, vPoint2.y);
						targetCast.graphics.lineTo(vPoint2.x, vPoint2.y + _halfSize);
						targetCast.graphics.lineTo(vPoint1.x, vPoint1.y + _halfSize);
						targetCast.graphics.lineTo(vPoint1.x - _halfSize, vPoint1.y);
						targetCast.graphics.lineTo(vPoint1.x, vPoint1.y - _halfSize);
						
					} else {
						
						// check for order of points based of y
						if(point1.y < point2.y) {
							vPoint1 = point1;
							vPoint2 = point2;
						} else {
							vPoint1 = point2;
							vPoint2 = point1;
						}
						
						// draw brush segment
						targetCast.graphics.moveTo(vPoint1.x + _halfSize, vPoint1.y);
						targetCast.graphics.lineTo(vPoint2.x + _halfSize, vPoint2.y);
						targetCast.graphics.lineTo(vPoint2.x, vPoint2.y + _halfSize);
						targetCast.graphics.lineTo(vPoint2.x - _halfSize, vPoint2.y);
						targetCast.graphics.lineTo(vPoint1.x - _halfSize, vPoint1.y);
						targetCast.graphics.lineTo(vPoint1.x, vPoint1.y - _halfSize);
						targetCast.graphics.lineTo(vPoint1.x + _halfSize, vPoint1.y);
						
					}
					
					targetCast.graphics.endFill();
					
				}
				
				
			}
			
		}
		
		/**
		* The <code>degrees</code> method will return a angle in degrees from a radians value.
		* 
		* @param radians Angle in radians
		* 
		*/
		private function degrees(radians:Number):Number {
			return radians * 180/Math.PI;
		}
		
	}
		
}